package com.jfinal.j2cache;

import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.j2cache.annotation.CacheEvict;
import com.jfinal.j2cache.annotation.CachePut;
import com.jfinal.j2cache.annotation.Cacheable;
import com.jfinal.j2cache.annotation.Caching;
import com.jfinal.j2cache.util.MD5;
import com.jfinal.j2cache.util.TokenReplaceTextManipulator;
import net.sf.cglib.beans.BeanMap;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CacheInterceptor implements Interceptor {
    @Override
    public void intercept(Invocation inv) {
        Method method = inv.getMethod();
        Annotation[] annotations = method.getAnnotations();
        Annotation cacheAnnocation = getCacheAnnotation(annotations);
        if (null != cacheAnnocation) {
            processCache(cacheAnnocation, inv.getTarget().getClass(), method, inv);
            return;
        } else {
            inv.invoke();
        }
    }

    private void processCache(Annotation annotation, Class<?> clazz, Method method, Invocation inv) {
        Class<? extends Annotation> type = annotation.annotationType();
        if (type == Cacheable.class) {
            processCacheable((Cacheable) annotation, clazz, method, inv);
        } else if (type == CacheEvict.class) {
            processCacheEvict((CacheEvict) annotation, clazz, method, inv);
        } else if (type == CachePut.class) {
            processCachePut((CachePut) annotation, clazz, method, inv);
        } else if (type == Caching.class) {
            processCaching((Caching) annotation, clazz, method, inv);
        }
    }

    /**
     * 缓存组合注解实现！！！
     */
    private void processCaching(Caching caching, Class<?> clazz, Method method, Invocation inv) {
        Cacheable[] cacheables = caching.cacheable();
        for (Cacheable cacheable : cacheables) {
            processCacheable(cacheable, clazz, method, inv);
        }

        CachePut[] cachePuts = caching.put();
        for (CachePut cachePut : cachePuts) {
            processCachePut(cachePut, clazz, method, inv);
        }

        CacheEvict[] cacheEvicts = caching.evict();
        for (CacheEvict cacheEvict : cacheEvicts) {
            processCacheEvict(cacheEvict, clazz, method, inv);
        }
    }

    private void processCachePut(CachePut cachePut, Class<?> clazz, Method method, Invocation inv) {
        //invoke 之后调用
        boolean error = isError(inv);

        if (!error) {
            //获取 CachePut 设定的缓存名称数组
            String[] cacheNames = cachePut.cacheNames();
            //获取指定的key值
            String cacheKey = cachePut.key();
            //获取替换后的真实缓存key值
            cacheKey = getRealKey(cacheKey, clazz, method, inv.getArgs());
            //获取方法的返回值
            Object result = inv.getReturnValue();
            if (null != result) {
                for (String cacheName : cacheNames) {
                    //替换指定缓存区域，指定缓存key的value
                    CacheFactory.getCacheManager().put(cacheName, cacheKey, result);
                }
            }
        }
    }

    private void processCacheEvict(CacheEvict cacheEvict, Class<?> clazz, Method method, Invocation inv) {
        //invoke 之后调用
        boolean error = isError(inv);
        if (!error) {
            //获取 CachePut 设定的缓存名称数组
            String[] cacheNames = cacheEvict.cacheNames();
            //获取指定的key值
            String cacheKey = cacheEvict.key();

            //如果没有指定当前缓存名称的全部缓存
            if (!cacheEvict.allEntires()) {
                //获取替换之后真实的缓存名称
                cacheKey = getRealKey(cacheKey, clazz, method, inv.getArgs());
            }

            //遍历清除对应区域的缓存
            for (String cacheName : cacheNames) {
                if (cacheEvict.allEntires()) {
                    CacheFactory.getCacheManager().clear(cacheName);
                } else {
                    CacheFactory.getCacheManager().evict(cacheName, cacheKey);
                }
            }
        }
    }

    private void processCacheable(Cacheable cacheable, Class<?> clazz, Method method, Invocation inv) {
        //获取指定的缓存名称数组
        String[] cacheNames = cacheable.cacheNames();
        //获取注解指定的缓存key
        String cacheKey = cacheable.key();
        //获取替换之后的真实缓存key
        cacheKey = getRealKey(cacheKey, clazz, method, inv.getArgs());
        //获取 缓存名称数组 中第一个名称虽对应的缓存key 的值
        Object result = CacheFactory.getCacheManager().get(cacheNames[0], cacheKey);
        if (null != result) {
            //获取到的缓存值直接返回！
            inv.setReturnValue(result);
            return;
        }
        boolean error = isError(inv);
        if (!error) {
            result = inv.getReturnValue();
            if (null != result) {
                for (String cacheName : cacheNames) {
                    CacheFactory.getCacheManager().put(cacheName, cacheKey, result);
                }
            }
        }
    }

    private boolean isError(Invocation inv) {
        boolean error = false;
        try {
            inv.invoke();
        } catch (Exception e) {
            error = true;
        }
        return error;
    }

    private String getRealKey(final String key, Class<?> clazz, Method method, Object[] args) {
        //定义替换方式
        TokenReplaceTextManipulator rt = new TokenReplaceTextManipulator("#{", "}", "");
        List<String> keys = rt.getKeys(new StringBuffer(key));

        Map<String, Object> tokenMap = new HashMap<>();
        for (String k : keys) {
            int dotPos = k.indexOf(".");
            Integer argPos;
            if (dotPos != -1) {
                String str[] = k.split("\\.");

                argPos = CacheArgsMapping.getPosOfArgs(clazz, method, str[0]);
                BeanMap beanMap = BeanMap.create(args[argPos]);
                Object value;
                if (args[argPos] instanceof Map) {//多条件
                    value = new MD5().getMD5(args[argPos].toString());
                } else {
                    value = beanMap.get(str[1]);
                }
                tokenMap.put(k, value);
            } else {
                argPos = CacheArgsMapping.getPosOfArgs(clazz, method, k);
                Object value = args[argPos];
                if (args[argPos] instanceof Map) {//多条件
                    value = new MD5().getMD5(args[argPos].toString());
                }
                tokenMap.put(k, value);
            }
        }

        return rt.manipulate(key, tokenMap);
    }

    private Annotation getCacheAnnotation(Annotation[] annotations) {
        if (null == annotations) {
            return null;
        }

        for (Annotation annotation : annotations) {
            Class<? extends Annotation> type = annotation.annotationType();
            if (type == Cacheable.class
                    || type == CacheEvict.class
                    || type == CachePut.class
                    || type == Caching.class) {
                return annotation;
            }
        }
        return null;
    }
}
